package cn.yhsoft.fs;

import cn.hutool.core.img.Img;
import cn.hutool.core.io.resource.InputStreamResource;
import cn.hutool.crypto.SecureUtil;
import cn.yhsoft.fs.config.FsTurboProperties;
import lombok.var;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * @author LiYong
 */
@Component
public class FsTurboService {
    private Logger log = LoggerFactory.getLogger(FsTurboService.class);
    @Autowired
    private FsTurboDao fsTurboDao;
    @Autowired
    private FsStorageAdapter storageAdapter;
    @Autowired
    private FsPathResolver pathResolver;
    @Autowired
    private FsTurboProperties properties;

    private int getInnerImgThumbSize() {
        return properties.getImageThumbSize() == null ? 64 : properties.getImageThumbSize();
    }

    public FsTurboService(FsTurboDao fsTurboDao, FsStorageAdapter storageAdapter) {
        this.fsTurboDao = fsTurboDao;
        this.storageAdapter = storageAdapter;
    }

    /**
     * 根据文件id获取文件实体
     *
     * @param id 文件id
     * @return 文件实体，如果是报错了将会返回null
     */
    public FsTurboDto getDtoById(String id) {
        FsTurboDto dto = null;
        try {
            dto = fsTurboDao.getById(id);
        } catch (Exception ex) {
            log.error("read file by id:" + id + " fail", ex);
        }
        if (dto == null) {
            log.error("read file by id:" + id + " fail");
        }
        return dto;
    }

    public List<FsTurboDto> getDtosByBusId(String scenes, String busId, int offset, int limit) {
        return fsTurboDao.getByBusId(scenes, busId, offset, limit);
    }

    /**
     * 上传文件
     *
     * @param dto         文件实体dto
     * @param inputStream 文件流
     * @param checkExist  是否要先根据id检测文件是否存在，如果为true且原来已经存在此id的情况下则直接return了
     */
    public void upload(FsTurboDto dto, InputStream inputStream, boolean checkExist) {
        ByteArrayOutputStream rawOutStream = new ByteArrayOutputStream();
        try {
            byte[] buffer = new byte[4096];
            int len;
            while ((len = inputStream.read(buffer)) > -1) {
                rawOutStream.write(buffer, 0, len);
            }
            rawOutStream.flush();
            inputStream.close();
        } catch (Exception ex) {
            throw new RuntimeException("获取原始文件流失败");
        }
        if (checkExist) {
            var hasSource = false;
            var md5Stream = new ByteArrayInputStream(rawOutStream.toByteArray());
            var md5 = SecureUtil.md5(md5Stream);
            try {
                md5Stream.close();
            } catch (Exception ignored) {
            }
            var oldDtos = fsTurboDao.getByIdOrMd5(dto.getId(), md5);
            //检测原来是否已经存在source，如果存在，那本次就需要设置为非source，也可能本次是非source，但是原来不存在source
            for (var item : oldDtos) {
                //为了效率考虑，数据库中实际是根据crc进行检索的，有一定的概率相同crc但存在不同md5的情况
                if (!item.getMd5().equals(dto.getMd5())) {
                    continue;
                }
                if (item.getId().equals(dto.getId())) {
                    return;
                }
                hasSource = true;
            }
            //这里直接进行设置，和原来是否有source没关系
            dto.setSource(!hasSource);
        }
        var dirPath = dto.getPath().substring(0, dto.getPath().lastIndexOf("/"));
        if (dto.getInnerType().equals(FsInnerTypes.IMAGE) && dto.isSource()) {
            //开始进行缩略图生成与存储
            try {
                ByteArrayOutputStream os = new ByteArrayOutputStream();
                int size = getInnerImgThumbSize();
                var toScaleStream = new ByteArrayInputStream(rawOutStream.toByteArray());
                Img.from(toScaleStream).scale(size, size, null).write(os);
                try {
                    toScaleStream.close();
                } catch (Exception ignored) {
                }
                InputStreamResource inputStreamResource = new InputStreamResource(new ByteArrayInputStream(os.toByteArray()), dto.getSaveFileName());
                storageAdapter.save(pathResolver.getThumbSavePath(dirPath), dto.getSaveFileName(), inputStreamResource.getStream());
                try {
                    inputStreamResource.getStream().close();
                } catch (Exception ignored) {
                }
            } catch (Exception ex) {
                log.error("从原图生成缩略图失败",ex);
                throw new RuntimeException("从原图生成缩略图失败");
            }
        }
        storageAdapter.save(dirPath, dto.getSaveFileName(), inputStream);
        fsTurboDao.insert(dto, dto.getInnerType().equals(FsInnerTypes.IMAGE));
        try {
            rawOutStream.close();
        } catch (Exception ignore) {
        }
    }

    /**
     * 通过文件id获取到文件实体及文件流，请在流使用后进行关闭
     *
     * @param id 文件id
     * @return 包含inputStream的实体，如果报错返回null
     */
    public FsTurboInputStream getInputStreamById(String id) {
        var dto = getDtoById(id);
        if (dto == null) {
            return null;
        }
        try {
            return FsTurboInputStream.builder().dto(dto).stream(storageAdapter.getInputStream(dto.getPath())).build();
        } catch (Exception ex) {
            log.error("read file:" + dto.getPath() + " fail", ex);
        }
        return null;
    }

    public List<FsTurboInputStream> getInputStreamsByIds(String[] ids) {
        var dtos = fsTurboDao.getByIds(ids);
        var result = new ArrayList<FsTurboInputStream>();
        if (dtos.size() == 0) {
            return result;
        }
        for (FsTurboDto dto : dtos) {
            result.add(FsTurboInputStream.builder().dto(dto).stream(storageAdapter.getInputStream(dto.getPath())).build());
        }
        return result;
    }

    public FsTurboDto getSourceFileByMd5(String fileMd5, boolean isImage) {
        return fsTurboDao.getSourceFileByMd5(fileMd5, isImage);
    }

    public FsTurboDto getSourceFileByMd5(String fileMd5) {
        return getSourceFileByMd5(fileMd5, false);
    }

    public int insert(FsTurboDto fsTurboDto, boolean isImage) {
        return fsTurboDao.insert(fsTurboDto, isImage);
    }

    public int deleteById(String id) {
        return fsTurboDao.deleteById(id);
    }

    public int deleteByScenesAndBus(String scenes, String busId) {
        return fsTurboDao.deleteByScenesAndBus(scenes, busId);
    }
}